#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h> /* PATH_MAX */
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/statfs.h>
#include <sys/syscall.h>
#include <sys/sendfile.h>
#include <sys/uio.h>
#include <dirent.h>
#include <pthread.h>
#include <fcntl.h>
#include <poll.h>
#include <printk.h>
#include "evtloop.h"
#include "linux-syscalls.h"
#include "fs.h"

#define INIT(subtype)                                                         \
  do {                                                                        \
    if (!req)                                                                 \
      return -EINVAL;                                                         \
    req->fs_type  = UV_FS_ ## subtype;                                        \
    req->result   = 0;                                                        \
    req->data     = NULL;                                                     \
    req->loop     = loop;                                                     \
    req->path     = NULL;                                                     \
    req->new_path = NULL;                                                     \
    req->bufs     = NULL;                                                     \
    req->cb       = cb;                                                       \
  } while (0)

#define PATH                                                                  \
  do {                                                                        \
    assert(path);                                                             \
    if (!cb) {                                                                \
      req->path = path;                                                       \
    } else {                                                                  \
      req->path = svc__strdup(path);                                          \
      if (!req->path)                                                         \
        return -ENOMEM;                                                       \
    }                                                                         \
  } while (0)


#define PATH2                                                                 \
  do {                                                                        \
    if (!cb) {                                                                \
      req->path = path;                                                       \
      req->new_path = new_path;                                               \
    } else {                                                                  \
      size_t path_len;                                                        \
      size_t new_path_len;                                                    \
      path_len = strlen(path) + 1;                                            \
      new_path_len = strlen(new_path) + 1;                                    \
      req->path = kmalloc(path_len + new_path_len);                           \
      if (req->path == NULL)                                                  \
        return -ENOMEM;                                                       \
      req->new_path = req->path + path_len;                                   \
      memcpy((void *)req->path, path, path_len);                              \
      memcpy((void *)req->new_path, new_path, new_path_len);                  \
    }                                                                         \
  } while (0)

#define POST                                                                  \
  do {                                                                        \
    if (cb != NULL) {                                                         \
      ev__req_add(loop, req);                                                 \
      svc__work_submit(loop, &req->work_req, uv__fs_work, uv__fs_done);       \
      return 0;                                                               \
    } else {                                                                  \
      uv__fs_work(&req->work_req);                                            \
      return req->result;                                                     \
    }                                                                         \
  } while (0)


int uv__cloexec_ioctl(int fd, int set)
{
    int r;

    do
        r = ioctl(fd, set ? FIOCLEX : FIONCLEX);
    while (r == -1 && errno == EINTR);

    if (r)
        return -errno;
    return 0;
}
int uv__close_nocancel(int fd)
{
    return syscall(SYS_close, fd);
}
int uv__getiovmax(void)
{
#if defined(IOV_MAX)
    return IOV_MAX;
#elif defined(_SC_IOV_MAX)
    static int iovmax = -1;
    if (iovmax == -1)
    {
        iovmax = sysconf(_SC_IOV_MAX);
        /* On some embedded devices (arm-linux-uclibc based ip camera),
         * sysconf(_SC_IOV_MAX) can not get the correct value. The return
         * value is -1 and the errno is EINPROGRESS. Degrade the value to 1.
         */
        if (iovmax == -1) iovmax = 1;
    }
    return iovmax;
#else
    return 1024;
#endif
}

static ssize_t uv__fs_open(ev_fs_t *req)
{
#ifdef O_CLOEXEC
    return open(req->path, req->flags | O_CLOEXEC, req->mode);
#else  /* O_CLOEXEC */
    int r;

    r = open(req->path, req->flags, req->mode);

    /* In case of failure `uv__cloexec` will leave error in `errno`,
     * so it is enough to just set `r` to `-1`.
     */
    if (r >= 0 && uv__cloexec_ioctl(r, 1) != 0)
    {
        r = uv__close_nocancel(r);
        if (r != 0)
            abort();
        r = -1;
    }
    return r;
#endif  /* O_CLOEXEC */
}
static int uv__fs_close(int fd)
{
    int rc;

    rc = uv__close_nocancel(fd);
    if (rc == -1)
        if (errno == EINTR || errno == EINPROGRESS)
            rc = 0;  /* The close is in progress, not an error. */
    return rc;
}

extern ssize_t pread (int __fd, void *__buf, size_t __nbytes, __off_t __offset);
extern ssize_t pwrite (int __fd, __const void *__buf, size_t __n, __off_t __offset);

#if !HAVE_PREADV
static ssize_t uv__fs_preadv(int fd, ev_buf_t *bufs, u32 nbufs, off_t off)
{
    ev_buf_t *buf;
    ev_buf_t *end;
    ssize_t result;
    ssize_t rc;
    size_t pos;

    assert(nbufs > 0);

    result = 0;
    pos = 0;
    buf = bufs + 0;
    end = bufs + nbufs;

    for (;;)
    {
        do
            rc = pread(fd, buf->base + pos, buf->len - pos, off + result);
        while (rc == -1 && errno == EINTR);

        if (rc == 0)
            break;

        if (rc == -1 && result == 0)
            return -errno;

        if (rc == -1)
            break;  /* We read some data so return that, ignore the error. */

        pos += rc;
        result += rc;

        if (pos < buf->len)
            continue;

        pos = 0;
        buf += 1;

        if (buf == end)
            break;
    }

    return result;
}
#endif
static ssize_t uv__fs_read(ev_fs_t *req)
{
    static int no_preadv;
    unsigned int iovmax;
    ssize_t result;

    iovmax = uv__getiovmax();
    if (req->nbufs > iovmax)
        req->nbufs = iovmax;

    if (req->off < 0)
    {
        if (req->nbufs == 1)
            result = read(req->file, req->bufs[0].base, req->bufs[0].len);
        else
            result = readv(req->file, (struct iovec *)req->bufs, req->nbufs);
    }
    else
    {
        if (req->nbufs == 1)
        {
            result = pread(req->file, req->bufs[0].base, req->bufs[0].len, req->off);
            goto done;
        }

#if HAVE_PREADV
        result = preadv(req->file, (struct iovec *)req->bufs, req->nbufs, req->off);
#else
# if defined(__linux__)
        if (no_preadv)
retry:
# endif
        {
            result = uv__fs_preadv(req->file, req->bufs, req->nbufs, req->off);
        }
# if defined(__linux__)
        else
        {
            result = uv__preadv(req->file,
                                (struct iovec *)req->bufs,
                                req->nbufs,
                                req->off);
            if (result == -1 && errno == ENOSYS)
            {
                no_preadv = 1;
                goto retry;
            }
        }
# endif
#endif
    }

done:
    /* Early cleanup of bufs allocation, since we're done with it. */
    if (req->bufs != req->bufsml)
        kfree(req->bufs);

    req->bufs = NULL;
    req->nbufs = 0;

#ifdef __PASE__
    /* PASE returns EOPNOTSUPP when reading a directory, convert to EISDIR */
    if (result == -1 && errno == EOPNOTSUPP)
    {
        struct stat buf;
        ssize_t rc;
        rc = fstat(req->file, &buf);
        if (rc == 0 && S_ISDIR(buf.st_mode))
        {
            errno = EISDIR;
        }
    }
#endif

    return result;
}
static ssize_t uv__fs_write(ev_fs_t *req)
{
#if defined(__linux__)
    static int no_pwritev;
#endif
    ssize_t r;

    /* Serialize writes on OS X, concurrent write() and pwrite() calls result in
     * data loss. We can't use a per-file descriptor lock, the descriptor may be
     * a dup().
     */
#if defined(__APPLE__)
    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

    if (pthread_mutex_lock(&lock))
        abort();
#endif

    if (req->off < 0)
    {
        if (req->nbufs == 1)
            r = write(req->file, req->bufs[0].base, req->bufs[0].len);
        else
            r = writev(req->file, (struct iovec *) req->bufs, req->nbufs);
    }
    else
    {
        if (req->nbufs == 1)
        {
            r = pwrite(req->file, req->bufs[0].base, req->bufs[0].len, req->off);
            goto done;
        }
#if HAVE_PREADV
        r = pwritev(req->file, (struct iovec *) req->bufs, req->nbufs, req->off);
#else
# if defined(__linux__)
if (no_pwritev) retry:
# endif
        {
            r = pwrite(req->file, req->bufs[0].base, req->bufs[0].len, req->off);
        }
# if defined(__linux__)
        else
        {
            r = uv__pwritev(req->file,
                            (struct iovec *) req->bufs,
                            req->nbufs,
                            req->off);
            if (r == -1 && errno == ENOSYS)
            {
                no_pwritev = 1;
                goto retry;
            }
        }
# endif
#endif
    }

done:
#if defined(__APPLE__)
    if (pthread_mutex_unlock(&lock))
        abort();
#endif

    return r;
}
static size_t uv__fs_buf_offset(ev_buf_t *bufs, size_t size)
{
    size_t offset;
    /* Figure out which bufs are done */
    for (offset = 0; size > 0 && bufs[offset].len <= size; ++offset)
        size -= bufs[offset].len;

    /* Fix a partial read/write */
    if (size > 0)
    {
        bufs[offset].base += size;
        bufs[offset].len -= size;
    }
    return offset;
}
static ssize_t uv__fs_write_all(ev_fs_t *req)
{
    unsigned int iovmax;
    unsigned int nbufs;
    ev_buf_t *bufs;
    ssize_t total;
    ssize_t result;

    iovmax = uv__getiovmax();
    nbufs = req->nbufs;
    bufs = req->bufs;
    total = 0;

    while (nbufs > 0)
    {
        req->nbufs = nbufs;
        if (req->nbufs > iovmax)
            req->nbufs = iovmax;

        do
            result = uv__fs_write(req);
        while (result < 0 && errno == EINTR);

        if (result <= 0)
        {
            if (total == 0)
                total = result;
            break;
        }

        if (req->off >= 0)
            req->off += result;

        req->nbufs = uv__fs_buf_offset(req->bufs, result);
        req->bufs += req->nbufs;
        nbufs -= req->nbufs;
        total += result;
    }

    if (bufs != req->bufsml)
        kfree(bufs);

    req->bufs = NULL;
    req->nbufs = 0;

    return total;
}
static ssize_t uv__fs_sendfile_emul(ev_fs_t *req)
{
    struct pollfd pfd;
    int use_pread;
    off_t offset;
    ssize_t nsent;
    ssize_t nread;
    ssize_t nwritten;
    size_t buflen;
    size_t len;
    ssize_t n;
    int in_fd;
    int out_fd;
    char buf[8192];

    len = req->bufsml[0].len;
    in_fd = req->flags;
    out_fd = req->file;
    offset = req->off;
    use_pread = 1;

    /* Here are the rules regarding errors:
     *
     * 1. Read errors are reported only if nsent==0, otherwise we return nsent.
     *    The user needs to know that some data has already been sent, to stop
     *    them from sending it twice.
     *
     * 2. Write errors are always reported. Write errors are bad because they
     *    mean data loss: we've read data but now we can't write it out.
     *
     * We try to use pread() and fall back to regular read() if the source fd
     * doesn't support positional reads, for example when it's a pipe fd.
     *
     * If we get EAGAIN when writing to the target fd, we poll() on it until
     * it becomes writable again.
     *
     * FIXME: If we get a write error when use_pread==1, it should be safe to
     *        return the number of sent bytes instead of an error because pread()
     *        is, in theory, idempotent. However, special files in /dev or /proc
     *        may support pread() but not necessarily return the same data on
     *        successive reads.
     *
     * FIXME: There is no way now to signal that we managed to send *some* data
     *        before a write error.
     */
    for (nsent = 0; (size_t) nsent < len;)
    {
        buflen = len - nsent;

        if (buflen > sizeof(buf))
            buflen = sizeof(buf);

        do
            if (use_pread)
                nread = pread(in_fd, buf, buflen, offset);
            else
                nread = read(in_fd, buf, buflen);
        while (nread == -1 && errno == EINTR);

        if (nread == 0)
            goto out;

        if (nread == -1)
        {
            if (use_pread && nsent == 0 && (errno == EIO || errno == ESPIPE))
            {
                use_pread = 0;
                continue;
            }

            if (nsent == 0)
                nsent = -1;

            goto out;
        }

        for (nwritten = 0; nwritten < nread;)
        {
            do
                n = write(out_fd, buf + nwritten, nread - nwritten);
            while (n == -1 && errno == EINTR);

            if (n != -1)
            {
                nwritten += n;
                continue;
            }

            if (errno != EAGAIN && errno != EWOULDBLOCK)
            {
                nsent = -1;
                goto out;
            }

            pfd.fd = out_fd;
            pfd.events = POLLOUT;
            pfd.revents = 0;

            do
                n = poll(&pfd, 1, -1);
            while (n == -1 && errno == EINTR);

            if (n == -1 || (pfd.revents & ~POLLOUT) != 0)
            {
                errno = EIO;
                nsent = -1;
                goto out;
            }
        }

        offset += nread;
        nsent += nread;
    }

out:
    if (nsent != -1)
        req->off = offset;

    return nsent;
}
static ssize_t uv__fs_sendfile(ev_fs_t *req)
{
    int in_fd;
    int out_fd;

    in_fd  = req->flags;
    out_fd = req->file;

#if defined(__linux__) || defined(__sun)
    {
        off_t off;
        ssize_t r;

        off = req->off;
        r = sendfile(out_fd, in_fd, &off, req->bufsml[0].len);

        /* sendfile() on SunOS returns EINVAL if the target fd is not a socket but
         * it still writes out data. Fortunately, we can detect it by checking if
         * the offset has been updated.
         */
        if (r != -1 || off > req->off)
        {
            r = off - req->off;
            req->off = off;
            return r;
        }
        if (errno == EINVAL || errno == EIO || errno == ENOTSOCK || errno == EXDEV)
        {
            errno = 0;
            return uv__fs_sendfile_emul(req);
        }

        return -1;
    }
#elif defined(__APPLE__)           || \
      defined(__DragonFly__)       || \
      defined(__FreeBSD__)         || \
      defined(__FreeBSD_kernel__)
    {
        off_t len;
        ssize_t r;

        /* sendfile() on FreeBSD and Darwin returns EAGAIN if the target fd is in
         * non-blocking mode and not all data could be written. If a non-zero
         * number of bytes have been sent, we don't consider it an error.
         */

#if defined(__FreeBSD__) || defined(__DragonFly__)
        len = 0;
        r = sendfile(in_fd, out_fd, req->off, req->bufsml[0].len, NULL, &len, 0);
#elif defined(__FreeBSD_kernel__)
        len = 0;
        r = bsd_sendfile(in_fd,
                         out_fd,
                         req->off,
                         req->bufsml[0].len,
                         NULL,
                         &len,
                         0);
#else
        /* The darwin sendfile takes len as an input for the length to send,
         * so make sure to initialize it with the caller's value. */
        len = req->bufsml[0].len;
        r = sendfile(in_fd, out_fd, req->off, &len, NULL, 0);
#endif

        /*
        * The man page for sendfile(2) on DragonFly states that `len` contains
        * a meaningful value ONLY in case of EAGAIN and EINTR.
        * Nothing is said about it's value in case of other errors, so better
        * not depend on the potential wrong assumption that is was not modified
        * by the syscall.
        */
        if (r == 0 || ((errno == EAGAIN || errno == EINTR) && len != 0))
        {
            req->off += len;
            return (ssize_t) len;
        }

        if (errno == EINVAL ||
                errno == EIO ||
                errno == ENOTSOCK ||
                errno == EXDEV)
        {
            errno = 0;
            return uv__fs_sendfile_emul(req);
        }

        return -1;
    }
#else
    /* Squelch compiler warnings. */
    (void) &in_fd;
    (void) &out_fd;

    return uv__fs_sendfile_emul(req);
#endif
}

static ssize_t uv__fs_fsync(ev_fs_t *req)
{
    return fsync(req->file);
}

static ssize_t uv__fs_fdatasync(ev_fs_t *req)
{
    return fdatasync(req->file);
}

static ssize_t uv__fs_mkdtemp(ev_fs_t *req)
{
    return mkdtemp((void *)req->path) ? 0 : -1;
}

#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_8)
#define UV_CONST_DIRENT uv__dirent_t
#else
#define UV_CONST_DIRENT const uv__dirent_t
#endif

static int uv__fs_scandir_filter(UV_CONST_DIRENT *dent)
{
    return strcmp(dent->d_name, ".") != 0 && strcmp(dent->d_name, "..") != 0;
}
static int uv__fs_scandir_sort(UV_CONST_DIRENT **a, UV_CONST_DIRENT **b)
{
    return strcmp((*a)->d_name, (*b)->d_name);
}
static ssize_t uv__fs_scandir(ev_fs_t *req)
{
    uv__dirent_t **dents;
    int n;

    dents = NULL;
    n = scandir(req->path, &dents, uv__fs_scandir_filter, uv__fs_scandir_sort);

    /* NOTE: We will use nbufs as an index field */
    req->nbufs = 0;

    if (n == 0)
    {
        /* OS X still needs to deallocate some memory.
         * Memory was allocated using the system allocator, so use free() here.
         */
        free(dents);
        dents = NULL;
    }
    else if (n == -1)
    {
        return n;
    }

    req->data = dents;

    return n;
}
static int uv__fs_opendir(ev_fs_t *req)
{
    uv_dir_t *dir;

    dir = kmalloc(sizeof(*dir));
    if (dir == NULL)
        goto error;

    dir->dir = opendir(req->path);
    if (dir->dir == NULL)
        goto error;

    req->data = dir;
    return 0;

error:
    kfree(dir);
    req->data = NULL;
    return -1;
}
uv_dirent_type_t uv__fs_get_dirent_type(uv__dirent_t *dent)
{
    uv_dirent_type_t type;

#ifdef HAVE_DIRENT_TYPES
    switch (dent->d_type)
    {
    case UV__DT_DIR:
        type = UV_DIRENT_DIR;
        break;
    case UV__DT_FILE:
        type = UV_DIRENT_FILE;
        break;
    case UV__DT_LINK:
        type = UV_DIRENT_LINK;
        break;
    case UV__DT_FIFO:
        type = UV_DIRENT_FIFO;
        break;
    case UV__DT_SOCKET:
        type = UV_DIRENT_SOCKET;
        break;
    case UV__DT_CHAR:
        type = UV_DIRENT_CHAR;
        break;
    case UV__DT_BLOCK:
        type = UV_DIRENT_BLOCK;
        break;
    default:
        type = UV_DIRENT_UNKNOWN;
    }
#else
    type = UV_DIRENT_UNKNOWN;
#endif

    return type;
}
static int uv__fs_readdir(ev_fs_t *req)
{
    uv_dir_t *dir;
    uv_dirent_t *dirent;
    struct dirent *res;
    unsigned int dirent_idx;
    unsigned int i;

    dir = req->data;
    dirent_idx = 0;

    while (dirent_idx < dir->nentries)
    {
        /* readdir() returns NULL on end of directory, as well as on error. errno
           is used to differentiate between the two conditions. */
        errno = 0;
        res = readdir(dir->dir);

        if (res == NULL)
        {
            if (errno != 0)
                goto error;
            break;
        }

        if (strcmp(res->d_name, ".") == 0 || strcmp(res->d_name, "..") == 0)
            continue;

        dirent = &dir->dirents[dirent_idx];
        dirent->name = svc__strdup(res->d_name);

        if (dirent->name == NULL)
            goto error;

        dirent->type = uv__fs_get_dirent_type(res);
        ++dirent_idx;
    }

    return dirent_idx;

error:
    for (i = 0; i < dirent_idx; ++i)
    {
        kfree((char *) dir->dirents[i].name);
        dir->dirents[i].name = NULL;
    }

    return -1;
}

static int uv__fs_closedir(ev_fs_t *req)
{
    uv_dir_t *dir;

    dir = req->data;

    if (dir->dir != NULL)
    {
        closedir(dir->dir);
        dir->dir = NULL;
    }

    kfree(req->data);
    req->data = NULL;
    return 0;
}

static ssize_t uv__fs_pathmax_size(const char *path)
{
    ssize_t pathmax;

    pathmax = pathconf(path, _PC_PATH_MAX);
    if (pathmax == -1)
        pathmax = PATH_MAX;
    return pathmax;
}

static ssize_t uv__fs_realpath(ev_fs_t *req)
{
    char *buf;
    ssize_t len;

    len = uv__fs_pathmax_size(req->path);
    buf = kmalloc(len + 1);
    if (!buf)
    {
        errno = ENOMEM;
        return -1;
    }

    if (realpath(req->path, buf) == NULL)
    {
        kfree(buf);
        return -1;
    }
    req->data = buf;
    return 0;
}

static void uv__to_stat(struct stat *src, ev_stat_t *dst)
{
    dst->st_dev     = src->st_dev;
    dst->st_mode    = src->st_mode;
    dst->st_nlink   = src->st_nlink;
    dst->st_uid     = src->st_uid;
    dst->st_gid     = src->st_gid;
    dst->st_rdev    = src->st_rdev;
    dst->st_ino     = src->st_ino;
    dst->st_size    = src->st_size;
    dst->st_blksize = src->st_blksize;
    dst->st_blocks  = src->st_blocks;
    dst->st_atim.tv_sec  = src->st_atime;
    dst->st_atim.tv_nsec = 0;
    dst->st_mtim.tv_sec  = src->st_mtime;
    dst->st_mtim.tv_nsec = 0;
    dst->st_ctim.tv_sec  = src->st_ctime;
    dst->st_ctim.tv_nsec = 0;
    dst->st_birthtim.tv_sec = src->st_ctime;
    dst->st_birthtim.tv_nsec = 0;
    dst->st_flags = 0;
    dst->st_gen = 0;
}

static int uv__fs_stat(ev_fs_t *req, const char *path)
{
    int ret;
    struct stat st;

    ev_stat_t *buf = kmalloc(sizeof(ev_stat_t));
    if (!buf)
    {
        errno = ENOMEM;
        return -1;
    }

    ret = stat(path, &st);
    if (ret < 0)
    {
        kfree(buf);
        return -1;
    }

    uv__to_stat(&st, buf);
    req->data = buf;
    return ret;
}

static void uv__fs_work(struct svc__work *w)
{
    int retry_on_eintr;
    ev_fs_t *req;
    ssize_t r;

    req = container_of(w, ev_fs_t, work_req);
    retry_on_eintr = !(req->fs_type == UV_FS_CLOSE ||
                       req->fs_type == UV_FS_READ);

    do
    {
        errno = 0;

#define X(type, action)                                                       \
  case UV_FS_ ## type:                                                        \
    r = action;                                                               \
    break;

        switch (req->fs_type)
        {
            X(OPEN,   uv__fs_open(req));
            X(CLOSE,  uv__fs_close(req->file));
            X(READ,   uv__fs_read(req));
            X(WRITE,  uv__fs_write_all(req));
            X(SENDFILE, uv__fs_sendfile(req));
            X(ACCESS, access(req->path, req->flags));
            X(CHMOD,  chmod(req->path, req->mode));
            X(CHOWN,  chown(req->path, req->uid, req->gid));
            X(FCHMOD, fchmod(req->file, req->mode));
            X(FCHOWN, fchown(req->file, req->uid, req->gid));
            X(LCHOWN, lchown(req->path, req->uid, req->gid));
            X(FDATASYNC, uv__fs_fdatasync(req));
            X(FSYNC,  uv__fs_fsync(req));
            X(FTRUNCATE, ftruncate(req->file, req->off));
            X(LINK,   link(req->path, req->new_path));
            X(SCANDIR, uv__fs_scandir(req));
            X(OPENDIR, uv__fs_opendir(req));
            X(READDIR, uv__fs_readdir(req));
            X(CLOSEDIR, uv__fs_closedir(req));
            X(MKDIR,  mkdir(req->path, req->mode));
            X(MKDTEMP,  uv__fs_mkdtemp(req));
            X(REALPATH, uv__fs_realpath(req));
            X(RENAME, rename(req->path, req->new_path));
            X(RMDIR,  rmdir(req->path));
            X(STAT,   uv__fs_stat(req, req->path));
            X(SYMLINK, symlink(req->path, req->new_path));
            X(UNLINK, unlink(req->path));
        default:
            abort();
        }
#undef X
    }
    while (r == -1 && errno == EINTR && retry_on_eintr);

    if (r == -1)
        req->result = -errno;
    else
        req->result = r;
}


static void uv__fs_done(struct svc__work *w, int status)
{
    ev_fs_t *req;

    req = container_of(w, ev_fs_t, work_req);
    ev__req_del(req->loop, req);

    if (status == -ECANCELED)
    {
        assert(req->result == 0);
        req->result = -ECANCELED;
    }
    req->cb(req);
}

int uv_fs_open(ev_loop_t *loop, ev_fs_t *req, const char *path, int flags, int mode, ev_fs_cb_t cb)
{
    INIT(OPEN);
    PATH;
    req->flags = flags;
    req->mode = mode;
    POST;
}
int uv_fs_close(ev_loop_t *loop, ev_fs_t *req, int file, ev_fs_cb_t cb)
{
    INIT(CLOSE);
    req->file = file;
    POST;
}
int uv_fs_read(ev_loop_t *loop, ev_fs_t *req, int file, const ev_buf_t bufs[], u32 nbufs, off_t off, ev_fs_cb_t cb)
{
    INIT(READ);

    if (bufs == NULL || nbufs == 0)
        return -EINVAL;

    req->file = file;

    req->nbufs = nbufs;
    req->bufs  = req->bufsml;
    if (nbufs > array_size(req->bufsml))
        req->bufs = kmalloc(nbufs * sizeof(*bufs));

    if (req->bufs == NULL)
        return -ENOMEM;

    memcpy(req->bufs, bufs, nbufs * sizeof(*bufs));

    req->off = off;
    POST;
}
int uv_fs_write(ev_loop_t *loop, ev_fs_t *req, int file, const ev_buf_t bufs[], u32 nbufs, off_t off, ev_fs_cb_t cb)
{
    INIT(WRITE);

    if (bufs == NULL || nbufs == 0)
        return -EINVAL;

    req->file = file;

    req->nbufs = nbufs;
    req->bufs = req->bufsml;
    if (nbufs > array_size(req->bufsml))
        req->bufs = kmalloc(nbufs * sizeof(*bufs));

    if (req->bufs == NULL)
        return -ENOMEM;

    memcpy(req->bufs, bufs, nbufs * sizeof(*bufs));

    req->off = off;
    POST;
}
int uv_fs_sendfile(ev_loop_t *loop, ev_fs_t *req, int out_fd, int in_fd, off_t in_offset, size_t length, ev_fs_cb_t cb)
{
    INIT(SENDFILE);
    req->flags = in_fd; /* hack */
    req->file  = out_fd;
    req->off   = in_offset;
    req->bufsml[0].len = length;
    POST;
}

int uv_fs_access(ev_loop_t *loop, ev_fs_t *req, const char *path, int flags, ev_fs_cb_t cb)
{
    INIT(ACCESS);
    PATH;
    req->flags = flags;
    POST;
}

int uv_fs_chmod(ev_loop_t *loop, ev_fs_t *req, const char *path, int mode, ev_fs_cb_t cb)
{
    INIT(CHMOD);
    PATH;
    req->mode = mode;
    POST;
}

int uv_fs_chown(ev_loop_t *loop, ev_fs_t *req, const char *path, ev_uid_t uid, ev_gid_t gid, ev_fs_cb_t cb)
{
    INIT(CHOWN);
    PATH;
    req->uid = uid;
    req->gid = gid;
    POST;
}

int uv_fs_fchmod(ev_loop_t *loop, ev_fs_t *req, int file, int mode, ev_fs_cb_t cb)
{
    INIT(FCHMOD);
    req->file = file;
    req->mode = mode;
    POST;
}

int uv_fs_fchown(ev_loop_t *loop, ev_fs_t *req, int file, ev_uid_t uid, ev_gid_t gid, ev_fs_cb_t cb)
{
    INIT(FCHOWN);
    req->file = file;
    req->uid = uid;
    req->gid = gid;
    POST;
}

int uv_fs_fsync(ev_loop_t *loop, ev_fs_t *req, int file, ev_fs_cb_t cb)
{
    INIT(FSYNC);
    req->file = file;
    POST;
}

int uv_fs_ftruncate(ev_loop_t *loop, ev_fs_t *req, int file, off_t off, ev_fs_cb_t cb)
{
    INIT(FTRUNCATE);
    req->file = file;
    req->off = off;
    POST;
}

int uv_fs_mkdir(ev_loop_t *loop, ev_fs_t *req, const char *path, int mode, ev_fs_cb_t cb)
{
    INIT(MKDIR);
    PATH;
    req->mode = mode;
    POST;
}

int uv_fs_realpath(ev_loop_t *loop, ev_fs_t *req, const char *path, ev_fs_cb_t cb)
{
    INIT(REALPATH);
    PATH;
    POST;
}

int uv_fs_rename(ev_loop_t *loop, ev_fs_t *req, const char *path, const char *new_path, ev_fs_cb_t cb)
{
    INIT(RENAME);
    PATH2;
    POST;
}

int uv_fs_scandir(ev_loop_t *loop, ev_fs_t *req, const char *path, int flags, ev_fs_cb_t cb)
{
    INIT(SCANDIR);
    PATH;
    req->flags = flags;
    POST;
}

int uv_fs_opendir(ev_loop_t *loop, ev_fs_t *req, const char *path, ev_fs_cb_t cb)
{
    INIT(OPENDIR);
    PATH;
    POST;
}

int uv_fs_readdir(ev_loop_t *loop, ev_fs_t *req, uv_dir_t *dir, ev_fs_cb_t cb)
{
    INIT(READDIR);

    if (dir == NULL || dir->dir == NULL || dir->dirents == NULL)
        return -EINVAL;

    req->data = dir;
    POST;
}

int uv_fs_closedir(ev_loop_t *loop, ev_fs_t *req, uv_dir_t *dir, ev_fs_cb_t cb)
{
    INIT(CLOSEDIR);

    if (dir == NULL)
        return -EINVAL;

    req->data = dir;
    POST;
}

int uv_fs_rmdir(ev_loop_t *loop, ev_fs_t *req, const char *path, ev_fs_cb_t cb)
{
    INIT(RMDIR);
    PATH;
    POST;
}

int uv_fs_stat(ev_loop_t *loop, ev_fs_t *req, const char *path, ev_fs_cb_t cb)
{
    INIT(STAT);
    PATH;
    POST;
}

int uv_fs_symlink(ev_loop_t *loop, ev_fs_t *req, const char *path, const char *new_path, int flags, ev_fs_cb_t cb)
{
    INIT(SYMLINK);
    PATH2;
    req->flags = flags;
    POST;
}

int uv_fs_unlink(ev_loop_t *loop, ev_fs_t *req, const char *path, ev_fs_cb_t cb)
{
    INIT(UNLINK);
    PATH;
    POST;
}

void uv__fs_readdir_cleanup(ev_fs_t *req)
{
    uv_dir_t *dir;
    uv_dirent_t *dirents;
    int i;

    if (req->data == NULL)
        return;

    dir = req->data;
    dirents = dir->dirents;
    req->data = NULL;

    if (dirents == NULL)
        return;

    for (i = 0; i < req->result; ++i)
    {
        kfree((char *) dirents[i].name);
        dirents[i].name = NULL;
    }
}
void uv__fs_scandir_cleanup(ev_fs_t *req)
{
    uv__dirent_t **dents;

    unsigned int *nbufs = &req->nbufs;

    dents = req->data;
    if (*nbufs > 0 && *nbufs != (unsigned int) req->result)
        (*nbufs)--;
    for (; *nbufs < (unsigned int) req->result; (*nbufs)++)
        kfree(dents[*nbufs]);

    kfree(req->data);
    req->data = NULL;
}

void uv_fs_req_cleanup(ev_fs_t *req)
{
    if (req == NULL)
        return;

    if (req->path && req->cb)
        kfree((void *)req->path); /* Memory is shared with req->new_path. */

    if (req->fs_type == UV_FS_READDIR && req->data != NULL)
        uv__fs_readdir_cleanup(req);

    if (req->fs_type == UV_FS_SCANDIR && req->data != NULL)
        uv__fs_scandir_cleanup(req);

    if (req->bufs != req->bufsml)
        kfree(req->bufs);
    req->bufs = NULL;

    req->path     = NULL;
    req->new_path = NULL;

    if (req->data)
        kfree(req->data);
    req->data = NULL;
}
